宽表在 BI 业务中比比皆是,每次建设 BI 系统时首先要做的就是准备宽表。有时系统中的宽表可能会有上千个字段,经常因为“过宽”超过了数据库表字段数量限制还要再拆分。为什么大家乐此不疲地造宽表呢?主要原因有两个。一是为了提高查询性能。现代 BI 通常使用关系数据库作为后台,而 SQL 通常使用的 HASH JOIN 算法,在关联表数量和关联层级变多的时候,计算性能会急剧下降,有七八个表三四层级关联时就能观察到这个现象,而 BI 业务中的关联复杂度远远超过这个规模,直接使用 SQL 的 JOIN 就无法达到前端立等可取的查询需要了。为了避免关联带来的性能问题,就要先将关联消除,即将多表事先关联好采用单表存储(也就是宽表),再查询的时候就可以不用再关联,从而达到提升查询性能的目的。二是为了降低业务难度。因为多表关联尤其是复杂关联在 BI 前端很难表达和使用。如果采用自动关联(根据字段类型等信息匹配)当遇到同维字段(如一个表有 2 个以上地区字段)时会“晕掉”不知道该关联哪个,表间循环关联或自关联的情况也无法处理;如果将众多表开放给用户来自行选择关联,由于业务用户无法理解表间关系而几乎没有可用性;分步关联可以描述复杂的关联需求,但一旦前一步出错就要推倒重来。所以,无论采用何种方式,工程实现和用户使用都很麻烦。但是基于单表来做就会简单很多,业务用户使用时没有什么障碍,因此将多表组织成宽表就成了“自然而然”的事情。不过,凡事都有两面性,我们看到宽表好处而大量应用的同时,其缺点也不容忽视,有些缺点会对应用产生极大影响。下面来看一下。
宽表的缺点
数据冗余容量大宽表不符合范式要求,将多个表合并成一个表会存在大量冗余数据,冗余程度跟原表数据量和表间关系有关,通常如果存在多层外键表,其冗余程度会呈指数级上升。大量数据冗余不仅会带来存储上的压力(多个表组合出来的宽表数量可能非常多)造成数据库容量问题,在查询计算时由于大量冗余数据参与运算还会影响计算性能,导致虽然用了宽表但仍然查询很慢。数据错误由于宽表不符合三范式要求,数据存储时可能出现一致性错误(脏写)。比如同一个销售员在不同记录中可能存储了不同的性别,同一个供应商在不同记录中的所在地可能出现矛盾。基于这样的数据做分析结果显然不对,而这种错误非常隐蔽很难被发现。另外,如果构建的宽表不合理还会出现汇总错误。比如基于一对多的 A 表和 B 表构建宽表,如果 A 中有计算指标(如金额),在宽表中就会重复,基于重复的指标再汇总就会出现错误。灵活性差宽表本质上是一种按需建模的手段,根据业务需求来构建宽表(虽然理论上可以把所有表的组合都形成宽表,但这只存在于理论上,如果要实际操作会发现需要的存储空间大到完全无法接受的程度),这就出现了一个矛盾:BI 系统建设的初衷主要是为了满足业务灵活查询的需要,即事先并不知道业务需求,有些查询是在业务开展过程中逐渐催生出来的,有些是业务用户临时起意的查询,这种灵活多变的需求采用宽表这种要事先加工的解决办法极为矛盾,想要获得宽表的好就得牺牲灵活性,可谓鱼与熊掌不可兼得。可用性问题除了以上问题,宽表由于字段过多还会引起可用性低的问题。一个事实表会对应多个维表,维表又有维表,而且表之间还可能存在自关联 / 循环关联的情况,这种结构在数据库系统中很常见,基于这些结构的表构建宽表,尤其要表达多个层级的时候,宽表字段数量会急剧增加,经常可能达到成百上千个(有的数据库表有字段数量限制,这时又要横向分表),试想一下,在用户接入界面如果出现上千个字段要怎么用?这就是宽表带来的可用性差的问题。总体来看,宽表的坏处在很多场景中经常要大于好处,那为什么宽表还大量横行呢?因为没办法。一直没有比宽表更好的方案来解决前面提到的查询性能和业务难度的问题。其实只要解决这两个问题,宽表就可以不用,由宽表产生的各类问题也就解决了。
SPL+DQL 消灭宽表
借助开源集算器 SPL 可以完成这个目标。SPL(Structured Process Language)是一个开源结构化数据计算引擎,本身提供了不依赖数据库的强大计算能力,SPL 内置了很多高性能算法,尤其是对关联运算做了优化,对不同的关联场景采用不同的手段,可以大幅提升关联性能,从而不用宽表也能实时关联以满足多维分析时效性的需要。同时,SPL 还提供了高性能存储,配合高效算法可以进一步发挥性能优势。只有高性能还不够,SPL 原生的计算语法不适合多维分析应用接入(生成 SPL 语句对 BI 系统改造较大)。目前大部分多维分析前端都是基于 SQL 开发的,但 SQL 体系(不用宽表时)在描述复杂关联计算上又很困难,基于这样的原因,SPL 设计了专门的类 SQL 查询语法 DQL(Dimensional Query Language)用于构建语义层。前端生成 DQL 语句,DQL Server 将其转换成 SPL 语句,再基于 SPL 计算引擎和存储引擎完成查询返回给前端,实现全链路 BI 查询。需要注意的是,SPL 只作为计算引擎存在,前端界面仍要由用户自行实现(或选用相应产品)。
SELECT ct1.area,o.emp_id,sum(o.amount) somtFROM orders o JOIN customer c ON o.cus_id = c.cus_id JOIN city ct1 ON c.city_id = ct1.city_id JOIN employee e ON o.emp_id = e.emp_id JOIN city ct2 ON e.city_id = ct2.city_idWHERE ct2.area = 'east' AND year(o.order_date)= 2022GROUP BY ct1.area, o.emp_id
多个表关联要 JOIN 多次,同一个地区表要反复关联两次才能查到销售员和客户的所在区域,对于这种情况 BI 前端表达起来会很吃力,如果将关联开放出来,用户又很难理解。
更进一步,我们再基于 DQL 开发 BI 前端界面就很容易,比如可以做成这样:用树结构分多级表达多层维表关联,这样的多维分析页面不仅容易开发,普通业务用户使用时也很容易理解,这就是 DQL 的效力。总结一下,宽表的目的是为了解决 BI 查询性能和前端工程实现问题,而宽表会带来数据冗余和灵活性差等问题。通过 SPL 的实时关联技术与高效存储可以解决性能问题,而且性能比宽表更高,同时不存在数据冗余,存储空间也更小(压缩);DQL 构建的语义层解决了多维分析前端工程的实现问题,让实时关联成为可能,,灵活性更高(不再局限于宽表的按需建模),界面也更容易实现,应用范围更广。SPL+DQL 继承(超越)宽表的优点同时改善其缺点,这才是 BI 该有的样子。